From 82d16dea6c3add54785baed3ebec6026fd30bdc9 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Tue, 21 Apr 2020 14:38:47 -0600 Subject: [PATCH] convert garmin_fit to Format class. (#544) Additionally, modernize fit reader: use a container to hold field information for a message. pass fit fields and message defs by const reference. --- CMakeLists.txt | 1 + GPSBabel.pro | 1 + Makefile.in | 59 ++--- garmin_fit.cc | 695 ++++++++++++++++--------------------------------- garmin_fit.h | 342 ++++++++++++++++++++++++ vecs.h | 4 +- 6 files changed, 606 insertions(+), 496 deletions(-) create mode 100644 garmin_fit.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d7c64e7e7..eb17c6561 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,7 @@ set(HEADERS format.h formspec.h garmin_device_xml.h + garmin_fit.h garmin_fs.h garmin_gpi.h garmin_icon_tables.h diff --git a/GPSBabel.pro b/GPSBabel.pro index 691b784ec..a2316cd4b 100644 --- a/GPSBabel.pro +++ b/GPSBabel.pro @@ -99,6 +99,7 @@ HEADERS = \ format.h \ formspec.h \ garmin_device_xml.h \ + garmin_fit.h \ garmin_fs.h \ garmin_gpi.h \ garmin_icon_tables.h \ diff --git a/Makefile.in b/Makefile.in index 03de36ec2..4d39de46f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -508,7 +508,7 @@ filter_vecs.o: filter_vecs.cc defs.h config.h zlib/zlib.h zlib/zconf.h \ duplicate.h height.h heightgrid.h interpolate.h nukedata.h polygon.h \ position.h radius.h reverse_route.h smplrout.h sort.h stackfilter.h \ swapdata.h trackfilter.h transform.h validate.h gbversion.h vecs.h \ - format.h energympro.h geojson.h src/core/file.h ggv_bin.h \ + format.h energympro.h garmin_fit.h geojson.h src/core/file.h ggv_bin.h \ globalsat_sport.h gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h \ legacyformat.h lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h \ shape.h shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \ @@ -533,18 +533,19 @@ garmin.o: garmin.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \ jeeps/gpsread.h jeeps/gpsutil.h jeeps/gpsapp.h jeeps/gpsprot.h \ jeeps/gpscom.h jeeps/gpsfmt.h jeeps/gpsmath.h jeeps/gpsmem.h \ jeeps/gpsrqst.h garmin_tables.h grtcirc.h jeeps/gpsserial.h vecs.h \ - energympro.h geojson.h src/core/file.h ggv_bin.h globalsat_sport.h \ - gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h legacyformat.h \ - lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h shape.h \ - shapelib/shapefil.h subrip.h xcsv.h src/core/textstream.h yahoo.h \ - xmlgeneric.h + energympro.h garmin_fit.h geojson.h src/core/file.h ggv_bin.h \ + globalsat_sport.h gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h \ + legacyformat.h lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h \ + shape.h shapelib/shapefil.h subrip.h xcsv.h src/core/textstream.h \ + yahoo.h xmlgeneric.h garmin_device_xml.o: garmin_device_xml.cc defs.h config.h zlib/zlib.h \ zlib/zconf.h formspec.h inifile.h gbfile.h session.h \ src/core/datetime.h src/core/optional.h garmin_device_xml.h \ xmlgeneric.h garmin_fit.o: garmin_fit.cc defs.h config.h zlib/zlib.h zlib/zconf.h \ formspec.h inifile.h gbfile.h session.h src/core/datetime.h \ - src/core/optional.h jeeps/gpsmath.h jeeps/gpsport.h + src/core/optional.h garmin_fit.h format.h jeeps/gpsmath.h \ + jeeps/gpsport.h garmin_fs.o: garmin_fs.cc defs.h config.h zlib/zlib.h zlib/zconf.h \ formspec.h inifile.h gbfile.h session.h src/core/datetime.h \ src/core/optional.h garmin_fs.h jeeps/gps.h jeeps/../defs.h \ @@ -819,10 +820,10 @@ maggeo.o: maggeo.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \ magproto.o: magproto.cc defs.h config.h zlib/zlib.h zlib/zconf.h \ formspec.h inifile.h gbfile.h session.h src/core/datetime.h \ src/core/optional.h explorist_ini.h format.h gbser.h magellan.h vecs.h \ - energympro.h geojson.h src/core/file.h ggv_bin.h globalsat_sport.h \ - gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h legacyformat.h \ - lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h shape.h \ - shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \ + energympro.h garmin_fit.h geojson.h src/core/file.h ggv_bin.h \ + globalsat_sport.h gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h \ + legacyformat.h lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h \ + shape.h shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \ jeeps/../defs.h jeeps/gpsport.h jeeps/gpsdevice.h jeeps/gpssend.h \ jeeps/gpsread.h jeeps/gpsutil.h jeeps/gpsapp.h jeeps/gpsprot.h \ jeeps/gpscom.h jeeps/gpsfmt.h jeeps/gpsmath.h jeeps/gpsmem.h \ @@ -833,15 +834,15 @@ main.o: main.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \ discard.h duplicate.h height.h heightgrid.h interpolate.h nukedata.h \ polygon.h position.h radius.h reverse_route.h smplrout.h sort.h \ stackfilter.h swapdata.h trackfilter.h transform.h validate.h format.h \ - src/core/file.h src/core/usasciicodec.h vecs.h energympro.h geojson.h \ - ggv_bin.h globalsat_sport.h gpx.h src/core/xmlstreamwriter.h \ - src/core/xmltag.h legacyformat.h lowranceusr.h mynav.h \ - qstarz_bl_1000.h nmea.h random.h shape.h shapelib/shapefil.h subrip.h \ - xcsv.h garmin_fs.h jeeps/gps.h jeeps/../defs.h jeeps/gpsport.h \ - jeeps/gpsdevice.h jeeps/gpssend.h jeeps/gpsread.h jeeps/gpsutil.h \ - jeeps/gpsapp.h jeeps/gpsprot.h jeeps/gpscom.h jeeps/gpsfmt.h \ - jeeps/gpsmath.h jeeps/gpsmem.h jeeps/gpsrqst.h src/core/textstream.h \ - yahoo.h xmlgeneric.h + src/core/file.h src/core/usasciicodec.h vecs.h energympro.h \ + garmin_fit.h geojson.h ggv_bin.h globalsat_sport.h gpx.h \ + src/core/xmlstreamwriter.h src/core/xmltag.h legacyformat.h \ + lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h shape.h \ + shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \ + jeeps/../defs.h jeeps/gpsport.h jeeps/gpsdevice.h jeeps/gpssend.h \ + jeeps/gpsread.h jeeps/gpsutil.h jeeps/gpsapp.h jeeps/gpsprot.h \ + jeeps/gpscom.h jeeps/gpsfmt.h jeeps/gpsmath.h jeeps/gpsmem.h \ + jeeps/gpsrqst.h src/core/textstream.h yahoo.h xmlgeneric.h mapasia.o: mapasia.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \ inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h mapbar_track.o: mapbar_track.cc defs.h config.h zlib/zlib.h zlib/zconf.h \ @@ -1056,15 +1057,15 @@ vcf.o: vcf.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \ jeeps/gpsmath.h jeeps/gpsport.h vecs.o: vecs.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \ inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h \ - vecs.h format.h energympro.h geojson.h src/core/file.h ggv_bin.h \ - globalsat_sport.h gpx.h src/core/xmlstreamwriter.h src/core/xmltag.h \ - legacyformat.h lowranceusr.h mynav.h qstarz_bl_1000.h nmea.h random.h \ - shape.h shapelib/shapefil.h subrip.h xcsv.h garmin_fs.h jeeps/gps.h \ - jeeps/../defs.h jeeps/gpsport.h jeeps/gpsdevice.h jeeps/gpssend.h \ - jeeps/gpsread.h jeeps/gpsutil.h jeeps/gpsapp.h jeeps/gpsprot.h \ - jeeps/gpscom.h jeeps/gpsfmt.h jeeps/gpsmath.h jeeps/gpsmem.h \ - jeeps/gpsrqst.h src/core/textstream.h yahoo.h xmlgeneric.h gbversion.h \ - src/core/logging.h + vecs.h format.h energympro.h garmin_fit.h geojson.h src/core/file.h \ + ggv_bin.h globalsat_sport.h gpx.h src/core/xmlstreamwriter.h \ + src/core/xmltag.h legacyformat.h lowranceusr.h mynav.h \ + qstarz_bl_1000.h nmea.h random.h shape.h shapelib/shapefil.h subrip.h \ + xcsv.h garmin_fs.h jeeps/gps.h jeeps/../defs.h jeeps/gpsport.h \ + jeeps/gpsdevice.h jeeps/gpssend.h jeeps/gpsread.h jeeps/gpsutil.h \ + jeeps/gpsapp.h jeeps/gpsprot.h jeeps/gpscom.h jeeps/gpsfmt.h \ + jeeps/gpsmath.h jeeps/gpsmem.h jeeps/gpsrqst.h src/core/textstream.h \ + yahoo.h xmlgeneric.h gbversion.h src/core/logging.h vidaone.o: vidaone.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \ inifile.h gbfile.h session.h src/core/datetime.h src/core/optional.h vitosmt.o: vitosmt.cc defs.h config.h zlib/zlib.h zlib/zconf.h formspec.h \ diff --git a/garmin_fit.cc b/garmin_fit.cc index fdc4b61a3..a50c24c5f 100644 --- a/garmin_fit.cc +++ b/garmin_fit.cc @@ -22,234 +22,76 @@ */ -#include -#include // for EOF, snprintf -#include -#include -#include - -#include // for QDateTime -#include // for QString +#include // for uint8_t, uint16_t, uint32_t, int32_t, int8_t, uint64_t +#include // for EOF, SEEK_SET, snprintf +#include // for deque, _Deque_iterator, operator!= +#include // for allocator_traits<>::value_type +#include // for pair +#include // for vector + +#include // for QByteArray +#include // for QDateTime +#include // for QString +#include // for CaseInsensitive #include "defs.h" -#include "gbfile.h" // for gbfgetc, gbfread, gbfclose, gbfgetuint16, gbfgetuint32, gbfile, gbfopen_le -#include "jeeps/gpsmath.h" // for GPS_Math_Semi_To_Deg +#include "garmin_fit.h" +#include "gbfile.h" // for gbfputc, gbfputuint16, gbfputuint32, gbfgetc, gbfread, gbfseek, gbfclose, gbfgetuint16, gbfopen_le, gbfputint32, gbfflush, gbfgetuint32, gbfputs, gbftell, gbfwrite, gbfile, gbsize_t +#include "jeeps/gpsmath.h" // for GPS_Math_Semi_To_Deg, GPS_Math_Gtime_To_Utime, GPS_Math_Deg_To_Semi, GPS_Math_Utime_To_Gtime #define MYNAME "fit" -// constants for global IDs -const int kIdFileId = 0; -const int kIdDeviceSettings = 0; -const int kIdLap = 19; -const int kIdRecord = 20; -const int kIdEvent = 21; -const int kIdCourse = 31; -const int kIdCoursePoint = 32; - -// constants for local IDs (for writing) -const int kWriteLocalIdFileId = 0; -const int kWriteLocalIdCourse = 1; -const int kWriteLocalIdLap = 2; -const int kWriteLocalIdEvent = 3; -const int kWriteLocalIdCoursePoint = 4; -const int kWriteLocalIdRecord = 5; - -// constants for message fields -// for all global IDs -const int kFieldTimestamp = 253; -const int kFieldMessageIndex = 254; -// for global ID: file id -const int kFieldType = 0; -const int kFieldManufacturer = 1; -const int kFieldProduct = 2; -const int kFieldTimeCreated = 4; -// for global ID: device settings -const int kFieldGlobalUtcOffset = 4; -// for global ID: lap -const int kFieldStartTime = 2; -const int kFieldStartLatitude = 3; -const int kFieldStartLongitude = 4; -const int kFieldEndLatitude = 5; -const int kFieldEndLongitude = 6; -const int kFieldElapsedTime = 7; -const int kFieldTotalTimerTime = 8; -const int kFieldTotalDistance = 9; -const int kFieldAvgSpeed = 13; -const int kFieldMaxSpeed = 14; -// for global ID: record -const int kFieldLatitude = 0; -const int kFieldLongitude = 1; -const int kFieldAltitude = 2; -const int kFieldHeartRate = 3; -const int kFieldCadence = 4; -const int kFieldDistance = 5; -const int kFieldSpeed = 6; -const int kFieldPower = 7; -const int kFieldTemperature = 13; -const int kFieldEnhancedSpeed = 73; -const int kFieldEnhancedAltitude = 78; -// for global ID: event -const int kFieldEvent = 0; -const int kEnumEventTimer = 0; -const int kFieldEventType = 1; -const int kEnumEventTypeStart = 0; -const int kFieldEventGroup = 4; -// for global ID: course -const int kFieldSport = 4; -const int kFieldName = 5; -// for global ID: course point -const int kFieldCPTimeStamp = 1; -const int kFieldCPPositionLat = 2; -const int kFieldCPPositionLong = 3; -const int kFieldCPDistance = 4; -const int kFieldCPName = 6; -const int kFieldCPType = 5; - -// For developer fields as a non conflicting id -const int kFieldInvalid = 255; - -// types for message definitions -const int kTypeEnum = 0x00; -const int kTypeUint8 = 0x02; -const int kTypeString = 0x07; -const int kTypeUint16 = 0x84; -const int kTypeSint32 = 0x85; -const int kTypeUint32 = 0x86; - -// misc. constants for message fields -const int kFileCourse = 0x06; -const int kEventTimer = 0x00; -const int kEventTypeStart = 0x00; -const int kEventTypeStopDisableAll = 0x09; -const int kCoursePointTypeGeneric = 0x00; -const int kCoursePointTypeLeft = 0x06; -const int kCoursePointTypeRight = 0x07; - -const int kWriteHeaderLen = 12; -const int kWriteHeaderCrcLen = 14; - -const double kSynthSpeed = 10.0 * 1000 / 3600; /* speed in m/s */ - -static char* opt_allpoints = nullptr; -static int lap_ct = 0; -static bool new_trkseg = false; -static bool write_header_msgs = false; - - -static -QVector fit_args = { - { - "allpoints", &opt_allpoints, - "Read all points even if latitude or longitude is missing", - nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr - }, -}; - -const std::vector > kCoursePointTypeMapping = { - {"left", kCoursePointTypeLeft}, - {"links", kCoursePointTypeLeft}, - {"gauche", kCoursePointTypeLeft}, - {"izquierda", kCoursePointTypeLeft}, - {"sinistra", kCoursePointTypeLeft}, - - {"right", kCoursePointTypeRight}, - {"rechts", kCoursePointTypeRight}, - {"droit", kCoursePointTypeRight}, - {"derecha", kCoursePointTypeRight}, - {"destro", kCoursePointTypeRight}, -}; - - -struct fit_field_t { - int id; - int size; - int type; -}; - -struct fit_message_def { - int endian; - int global_id; - int num_fields; - fit_field_t* fields; -}; - -static struct { - int len; - int endian; - route_head* track; - uint32_t last_timestamp; - uint32_t global_utc_offset; - fit_message_def message_def[16]; -} fit_data; - -struct FitCourseRecordPoint { - FitCourseRecordPoint(const Waypoint &wpt, bool is_course_point, unsigned int course_point_type = kCoursePointTypeGeneric) - : lat(wpt.latitude), - lon(wpt.longitude), - altitude(wpt.altitude), - speed(WAYPT_HAS((&wpt), speed) ? wpt.speed : -1), - odometer_distance(wpt.odometer_distance), - creation_time(wpt.creation_time), - shortname(wpt.shortname), - is_course_point(is_course_point), - course_point_type(course_point_type) { } - double lat, lon, altitude; - double speed, odometer_distance; - gpsbabel::DateTime creation_time; - QString shortname; - bool is_course_point; - unsigned int course_point_type; -}; - -std::deque course, waypoints; - - -static gbfile* fin; -static gbfile* fout; +// Until c++17 we have to define odr-used constexpr static data members at namespace scope. +#if __cplusplus < 201703L +constexpr int GarminFitFormat::kTypeEnum; +constexpr int GarminFitFormat::kTypeUint8; +constexpr int GarminFitFormat::kTypeString; +constexpr int GarminFitFormat::kTypeUint16; +constexpr int GarminFitFormat::kTypeSint32; +constexpr int GarminFitFormat::kTypeUint32; +constexpr int GarminFitFormat::kCoursePointTypeLeft; +constexpr int GarminFitFormat::kCoursePointTypeRight; +#endif /******************************************************************************* * %%% global callbacks called by gpsbabel main process %%% * *******************************************************************************/ -static void -fit_rd_init(const QString& fname) +void +GarminFitFormat::rd_init(const QString& fname) { fin = gbfopen_le(fname, "rb", MYNAME); } -static void -fit_rd_deinit() +void +GarminFitFormat::rd_deinit() { - for (auto &local_id : fit_data.message_def) { - fit_message_def* def = &local_id; - if (def->fields) { - xfree(def->fields); - def->fields = nullptr; - } + for (auto& local_id : fit_data.message_def) { + // QList::clear() will not deallocate + local_id.fields = QList(); } gbfclose(fin); } -static void -fit_wr_init(const QString& fname) +void +GarminFitFormat::wr_init(const QString& fname) { fout = gbfopen_le(fname, "w+b", MYNAME); } -static void -fit_wr_deinit() +void +GarminFitFormat::wr_deinit() { gbfclose(fout); } - /******************************************************************************* * fit_parse_header- parse the global FIT header *******************************************************************************/ -static void -fit_parse_header() +void +GarminFitFormat::fit_parse_header() { char sig[4]; @@ -293,8 +135,8 @@ fit_parse_header() fit_data.global_utc_offset = 0; } -static uint8_t -fit_getuint8() +uint8_t +GarminFitFormat::fit_getuint8() { if (fit_data.len == 0) { // fail gracefully for GARMIN Edge 800 with newest firmware, seems to write a wrong record length @@ -311,11 +153,10 @@ fit_getuint8() } fit_data.len--; return (uint8_t)val; - } -static uint16_t -fit_getuint16() +uint16_t +GarminFitFormat::fit_getuint16() { char buf[2]; @@ -330,11 +171,10 @@ fit_getuint16() } else { return le_read16(buf); } - } -static uint32_t -fit_getuint32() +uint32_t +GarminFitFormat::fit_getuint32() { char buf[4]; @@ -349,22 +189,19 @@ fit_getuint32() } else { return le_read32(buf); } - } -static void -fit_parse_definition_message(uint8_t header) +void +GarminFitFormat::fit_parse_definition_message(uint8_t header) { int local_id = header & 0x0f; fit_message_def* def = &fit_data.message_def[local_id]; - if (def->fields) { - xfree(def->fields); - } + def->fields = QList(); // first byte is reserved. It's usually 0 and we don't know what it is, // but we've seen some files that are 0x40. So we just read it and toss it. - int i = fit_getuint8(); + (void) fit_getuint8(); // second byte is endianness def->endian = fit_getuint8(); @@ -377,49 +214,44 @@ fit_parse_definition_message(uint8_t header) def->global_id = fit_getuint16(); // byte 5 has the number of records in the remainder of the definition message - def->num_fields = fit_getuint8(); + int num_fields = fit_getuint8(); if (global_opts.debug_level >= 8) { - debug_print(8,"%s: definition message contains %d records\n",MYNAME, def->num_fields); - } - if (def->num_fields == 0) { - def->fields = (fit_field_t*) xmalloc(sizeof(fit_field_t)); + debug_print(8,"%s: definition message contains %d records\n",MYNAME, num_fields); } // remainder of the definition message is data at one byte per field * 3 fields - if (def->num_fields > 0) { - def->fields = (fit_field_t*) xmalloc(def->num_fields * sizeof(fit_field_t)); - for (i = 0; i < def->num_fields; i++) { - def->fields[i].id = fit_getuint8(); - def->fields[i].size = fit_getuint8(); - def->fields[i].type = fit_getuint8(); - if (global_opts.debug_level >= 8) { - debug_print(8,"%s: record %d ID: %d SIZE: %d TYPE: %d fit_data.len=%d\n", - MYNAME, i, def->fields[i].id, def->fields[i].size, def->fields[i].type,fit_data.len); - } - + for (int i = 0; i < num_fields; ++i) { + int id = fit_getuint8(); + int size = fit_getuint8(); + int type = fit_getuint8(); + fit_field_t field = {id, size, type}; + if (global_opts.debug_level >= 8) { + debug_print(8,"%s: record %d ID: %d SIZE: %d TYPE: %d fit_data.len=%d\n", + MYNAME, i, field.id, field.size, field.type, fit_data.len); } + def->fields.append(field); } // If we have developer fields (since version 2.0) they must be read too // These is one byte containing the number of fields and 3 bytes for every field. // So this is identical to the normal fields but the meaning of the content is different. // - // Currently we just want to ignore the developer fields because they are not meant + // Currently we just want to ignore the developer fields because they are not meant // to hold relevant data we need (currently handle) for the conversion. // For simplicity using the existing infrastructure we do it in the following way: - // * We read it in as normal fields - // * We set the field id to kFieldInvalid so that it do not interfere with valid id's from - // the normal fields. - // -In our opinion in practice this will not happen, because we do not expect + // * We read it in as normal fields + // * We set the field id to kFieldInvalid so that it do not interfere with valid id's from + // the normal fields. + // -In our opinion in practice this will not happen, because we do not expect // developer fields e.g. inside lap or record records. But we want to be safe here. // * We do not have to change the type as we did for the id above, because fit_read_field() - // already uses the size information to read the data, if the type does not match the size. - // + // already uses the size information to read the data, if the type does not match the size. + // // If we want to change this or if we want to avoid the xrealloc call, we can change // it in the future by e.g. extending the fit_message_def struct. - // Bit 5 of the header specify if we have developer fields in the data message + // Bit 5 of the header specify if we have developer fields in the data message bool hasDevFields = static_cast(header & 0x20); if (hasDevFields) { @@ -431,27 +263,26 @@ fit_parse_definition_message(uint8_t header) return; } - int numOfFields = def->num_fields+numOfDevFields; - def->fields = (fit_field_t*) xrealloc(def->fields, numOfFields * sizeof(fit_field_t)); - for (i = def->num_fields; i < numOfFields; i++) { - def->fields[i].id = fit_getuint8(); - def->fields[i].size = fit_getuint8(); - def->fields[i].type = fit_getuint8(); + int numOfFields = num_fields + numOfDevFields; + for (int i = num_fields; i < numOfFields; ++i) { + int id = fit_getuint8(); + int size = fit_getuint8(); + int type = fit_getuint8(); + fit_field_t field = {id, size, type}; if (global_opts.debug_level >= 8) { debug_print(8,"%s: developer record %d ID: %d SIZE: %d TYPE: %d fit_data.len=%d\n", - MYNAME, i-def->num_fields, def->fields[i].id, def->fields[i].size, def->fields[i].type,fit_data.len); + MYNAME, i - num_fields, field.id, field.size, field.type, fit_data.len); } - // Because we parse developer fields like normal fields and we do not want + // Because we parse developer fields like normal fields and we do not want // that the field id interfere which valid id's from the normal fields - def->fields[i].id = kFieldInvalid; - + field.id = kFieldInvalid; + def->fields.append(field); } - def->num_fields = numOfFields; } } -static uint32_t -fit_read_field(fit_field_t* f) +uint32_t +GarminFitFormat::fit_read_field(const fit_field_t& f) { /* https://forums.garmin.com/showthread.php?223645-Vivoactive-problems-plus-suggestions-for-future-firmwares&p=610929#post610929 * Per section 4.2.1.4.2 of the FIT Protocol the size of a field may be a @@ -462,20 +293,19 @@ fit_read_field(fit_field_t* f) */ // In the case that the field contains one value of the indicated type we return that value, // otherwise we just skip over the data. - int i; if (global_opts.debug_level >= 8) { - debug_print(8,"%s: fit_read_field: read data field with f->type=0x%X and f->size=%d fit_data.len=%d\n", - MYNAME, f->type, f->size, fit_data.len); + debug_print(8,"%s: fit_read_field: read data field with f.type=0x%X and f.size=%d fit_data.len=%d\n", + MYNAME, f.type, f.size, fit_data.len); } - switch (f->type) { + switch (f.type) { case 0: // enum case 1: // sint8 case 2: // uint8 - if (f->size == 1) { + if (f.size == 1) { return fit_getuint8(); } else { // ignore array data - for (i = 0; i < f->size; i++) { + for (int i = 0; i < f.size; ++i) { fit_getuint8(); } if (global_opts.debug_level >= 8) { @@ -485,10 +315,10 @@ fit_read_field(fit_field_t* f) } case 0x83: // sint16 case 0x84: // uint16 - if (f->size == 2) { + if (f.size == 2) { return fit_getuint16(); } else { // ignore array data - for (i = 0; i < f->size; i++) { + for (int i = 0; i < f.size; ++i) { fit_getuint8(); } if (global_opts.debug_level >= 8) { @@ -498,10 +328,10 @@ fit_read_field(fit_field_t* f) } case 0x85: // sint32 case 0x86: // uint32 - if (f->size == 4) { + if (f.size == 4) { return fit_getuint32(); } else { // ignore array data - for (i = 0; i < f->size; i++) { + for (int i = 0; i < f.size; ++i) { fit_getuint8(); } if (global_opts.debug_level >= 8) { @@ -510,7 +340,7 @@ fit_read_field(fit_field_t* f) return -1; } default: // Ignore everything else for now. - for (i = 0; i < f->size; i++) { + for (int i = 0; i < f.size; ++i) { fit_getuint8(); } if (global_opts.debug_level >= 8) { @@ -520,8 +350,8 @@ fit_read_field(fit_field_t* f) } } -static void -fit_parse_data(fit_message_def* def, int time_offset) +void +GarminFitFormat::fit_parse_data(const fit_message_def& def, int time_offset) { uint32_t timestamp = fit_data.last_timestamp + time_offset; int32_t lat = 0x7fffffff; @@ -532,7 +362,6 @@ fit_parse_data(fit_message_def* def, int time_offset) uint8_t cadence = 0xff; uint16_t power = 0xffff; int8_t temperature = 0x7f; - Waypoint* waypt; int32_t startlat = 0x7fffffff; int32_t startlon = 0x7fffffff; int32_t endlat = 0x7fffffff; @@ -540,32 +369,31 @@ fit_parse_data(fit_message_def* def, int time_offset) uint32_t starttime = 0; // ??? default ? uint8_t event = 0xff; uint8_t eventtype = 0xff; - char cbuf[10]; - Waypoint* lappt; // WptPt in gpx if (global_opts.debug_level >= 7) { - debug_print(7,"%s: parsing fit data ID %d with num_fields=%d\n", MYNAME, def->global_id, def->num_fields); + debug_print(7,"%s: parsing fit data ID %d with num_fields=%d\n", MYNAME, def.global_id, def.fields.size()); } - for (int i = 0; i < def->num_fields; i++) { + for (int i = 0; i < def.fields.size(); ++i) { if (global_opts.debug_level >= 7) { debug_print(7,"%s: parsing field %d\n", MYNAME, i); } - fit_field_t* f = &def->fields[i]; + const fit_field_t& f = def.fields.at(i); uint32_t val = fit_read_field(f); - if (f->id == kFieldTimestamp) { + if (f.id == kFieldTimestamp) { if (global_opts.debug_level >= 7) { debug_print(7,"%s: parsing fit data: timestamp=%d\n", MYNAME, val); } timestamp = val; // if the timestamp is < 0x10000000, this value represents // system time; to convert it to UTC, add the global utc offset to it - if (timestamp < 0x10000000) + if (timestamp < 0x10000000) { timestamp += fit_data.global_utc_offset; + } fit_data.last_timestamp = timestamp; } else { - switch (def->global_id) { + switch (def.global_id) { case kIdDeviceSettings: // device settings message - switch (f->id) { + switch (f.id) { case kFieldGlobalUtcOffset: if (global_opts.debug_level >= 7) { debug_print(7,"%s: parsing fit data: global utc_offset=%d\n", MYNAME, val); @@ -574,15 +402,15 @@ fit_parse_data(fit_message_def* def, int time_offset) break; default: if (global_opts.debug_level >= 1) { - debug_print(1, "%s: unrecognized data type in GARMIN FIT device settings: f->id=%d\n", MYNAME, f->id); + debug_print(1, "%s: unrecognized data type in GARMIN FIT device settings: f.id=%d\n", MYNAME, f.id); } break; - } // switch (f->id) - // end of case def->global_id = kIdDeviceSettings + } // switch (f.id) + // end of case def.global_id = kIdDeviceSettings break; case kIdRecord: // record message - trkType is a track - switch (f->id) { + switch (f.id) { case kFieldLatitude: if (global_opts.debug_level >= 7) { debug_print(7,"%s: parsing fit data: lat=%d\n", MYNAME, val); @@ -600,7 +428,7 @@ fit_parse_data(fit_message_def* def, int time_offset) debug_print(7,"%s: parsing fit data: alt=%d\n", MYNAME, val); } if (val != 0xffff) { - alt = val; + alt = val; } break; case kFieldHeartRate: @@ -618,7 +446,7 @@ fit_parse_data(fit_message_def* def, int time_offset) case kFieldDistance: // NOTE: 5 is DISTANCE in cm ... unused. if (global_opts.debug_level >= 7) { - debug_print(7, "%s: unrecognized data type in GARMIN FIT record: f->id=%d\n", MYNAME, f->id); + debug_print(7, "%s: unrecognized data type in GARMIN FIT record: f.id=%d\n", MYNAME, f.id); } break; case kFieldSpeed: @@ -626,7 +454,7 @@ fit_parse_data(fit_message_def* def, int time_offset) debug_print(7,"%s: parsing fit data: speed=%d\n", MYNAME, val); } if (val != 0xffff) { - speed = val; + speed = val; } break; case kFieldPower: @@ -646,7 +474,7 @@ fit_parse_data(fit_message_def* def, int time_offset) debug_print(7,"%s: parsing fit data: enhanced_speed=%d\n", MYNAME, val); } if (val != 0xffff) { - speed = val; + speed = val; } break; case kFieldEnhancedAltitude: @@ -654,20 +482,20 @@ fit_parse_data(fit_message_def* def, int time_offset) debug_print(7,"%s: parsing fit data: enhanced_altitude=%d\n", MYNAME, val); } if (val != 0xffff) { - alt = val; + alt = val; } break; default: if (global_opts.debug_level >= 1) { - debug_print(1, "%s: unrecognized data type in GARMIN FIT record: f->id=%d\n", MYNAME, f->id); + debug_print(1, "%s: unrecognized data type in GARMIN FIT record: f.id=%d\n", MYNAME, f.id); } break; - } // switch (f->id) - // end of case def->global_id = kIdRecord + } // switch (f.id) + // end of case def.global_id = kIdRecord break; case kIdLap: // lap wptType , endlat+lon is wpt - switch (f->id) { + switch (f.id) { case kFieldStartTime: if (global_opts.debug_level >= 7) { debug_print(7,"%s: parsing fit data: starttime=%d\n", MYNAME, val); @@ -712,15 +540,15 @@ fit_parse_data(fit_message_def* def, int time_offset) break; default: if (global_opts.debug_level >= 1) { - debug_print(1, "%s: unrecognized data type in GARMIN FIT lap: f->id=%d\n", MYNAME, f->id); + debug_print(1, "%s: unrecognized data type in GARMIN FIT lap: f.id=%d\n", MYNAME, f.id); } break; - } // switch (f->id) - // end of case def->global_id = kIdLap + } // switch (f.id) + // end of case def.global_id = kIdLap break; case kIdEvent: - switch (f->id) { + switch (f.id) { case kFieldEvent: if (global_opts.debug_level >= 7) { debug_print(7,"%s: parsing fit data: event=%d\n", MYNAME, val); @@ -733,43 +561,44 @@ fit_parse_data(fit_message_def* def, int time_offset) } eventtype = val; break; - } // switch (f->id) - // end of case def->global_id = kIdEvent + } // switch (f.id) + // end of case def.global_id = kIdEvent break; default: if (global_opts.debug_level >= 1) { - debug_print(1, "%s: unrecognized/unhandled global ID for GARMIN FIT: %d\n", MYNAME, def->global_id); + debug_print(1, "%s: unrecognized/unhandled global ID for GARMIN FIT: %d\n", MYNAME, def.global_id); } break; - } // switch (def->global_id) + } // switch (def.global_id) } } if (global_opts.debug_level >= 7) { - debug_print(7,"%s: storing fit data with num_fields=%d\n", MYNAME, def->num_fields); + debug_print(7,"%s: storing fit data with num_fields=%d\n", MYNAME, def.fields.size()); } - switch (def->global_id) { - case kIdLap: // lap message + switch (def.global_id) { + case kIdLap: { // lap message if (endlat == 0x7fffffff || endlon == 0x7fffffff) { break; } if (global_opts.debug_level >= 7) { - debug_print(7,"%s: storing fit data LAP %d\n", MYNAME, def->global_id); + debug_print(7,"%s: storing fit data LAP %d\n", MYNAME, def.global_id); } - lappt = new Waypoint; + auto* lappt = new Waypoint; lappt->latitude = GPS_Math_Semi_To_Deg(endlat); lappt->longitude = GPS_Math_Semi_To_Deg(endlon); - lap_ct++; - snprintf(cbuf, sizeof(cbuf), "LAP%03d", lap_ct); + char cbuf[10]; + snprintf(cbuf, sizeof(cbuf), "LAP%03d", ++lap_ct); lappt->shortname = cbuf; waypt_add(lappt); - break; - case kIdRecord: // record message + } + break; + case kIdRecord: { // record message if ((lat == 0x7fffffff || lon == 0x7fffffff) && !opt_allpoints) { break; } - waypt = new Waypoint; + auto* waypt = new Waypoint; if (lat != 0x7fffffff) { waypt->latitude = GPS_Math_Semi_To_Deg(lat); } @@ -800,7 +629,8 @@ fit_parse_data(fit_message_def* def, int time_offset) new_trkseg = false; } track_add_wpt(fit_data.track, waypt); - break; + } + break; case kIdEvent: // event message if (event == kEnumEventTimer && eventtype == kEnumEventTypeStart) { // Start event, start new track segment. Note: We don't do this @@ -813,35 +643,33 @@ fit_parse_data(fit_message_def* def, int time_offset) } } -static void -fit_parse_data_message(uint8_t header) +void +GarminFitFormat::fit_parse_data_message(uint8_t header) { int local_id = header & 0x0f; - fit_message_def* def = &fit_data.message_def[local_id]; - fit_parse_data(def, 0); + fit_parse_data(fit_data.message_def[local_id], 0); } -static void -fit_parse_compressed_message(uint8_t header) +void +GarminFitFormat::fit_parse_compressed_message(uint8_t header) { int local_id = (header >> 5) & 3; - fit_message_def* def = &fit_data.message_def[local_id]; - fit_parse_data(def, header & 0x1f); + fit_parse_data(fit_data.message_def[local_id], header & 0x1f); } /******************************************************************************* * fit_parse_record- parse each record in the file *******************************************************************************/ -static void -fit_parse_record() +void +GarminFitFormat::fit_parse_record() { uint8_t header = fit_getuint8(); // high bit 7 set -> compressed message (0 for normal) // second bit 6 set -> 0 for data message, 1 for definition message - // bit 5 -> message type specific - // definition message: Bit set means that we have additional Developer Field definitions + // bit 5 -> message type specific + // definition message: Bit set means that we have additional Developer Field definitions // behind the field definitions inside the record content - // data message: currently not used + // data message: currently not used // bit 4 -> reserved // bits 3..0 -> local message type if (header & 0x80) { @@ -870,8 +698,8 @@ fit_parse_record() * - parse the header * - parse all the records in the file *******************************************************************************/ -static void -fit_read() +void +GarminFitFormat::read() { fit_parse_header(); @@ -889,71 +717,24 @@ fit_read() * FIT writing *******************************************************************************/ -const static std::vector fit_msg_fields_file_id = { - // field id, size, type - { kFieldType, 0x01, kTypeEnum }, - { kFieldManufacturer, 0x02, kTypeUint16 }, - { kFieldProduct, 0x02, kTypeUint16 }, - { kFieldTimeCreated, 0x04, kTypeUint32 }, -}; -const static std::vector fit_msg_fields_course = { - { kFieldName, 0x10, kTypeString }, - { kFieldSport, 0x01, kTypeEnum }, -}; -const static std::vector fit_msg_fields_lap = { - { kFieldTimestamp, 0x04, kTypeUint32 }, - { kFieldStartTime, 0x04, kTypeUint32 }, - { kFieldStartLatitude, 0x04, kTypeSint32 }, - { kFieldStartLongitude, 0x04, kTypeSint32 }, - { kFieldEndLatitude, 0x04, kTypeSint32 }, - { kFieldEndLongitude, 0x04, kTypeSint32 }, - { kFieldElapsedTime, 0x04, kTypeUint32 }, - { kFieldTotalTimerTime, 0x04, kTypeUint32 }, - { kFieldTotalDistance, 0x04, kTypeUint32 }, - { kFieldAvgSpeed, 0x02, kTypeUint16 }, - { kFieldMaxSpeed, 0x02, kTypeUint16 }, -}; -const static std::vector fit_msg_fields_event = { - { kFieldTimestamp, 0x04, kTypeUint32 }, - { kFieldEvent, 0x01, kTypeEnum }, - { kFieldEventType, 0x01, kTypeEnum }, - { kFieldEventGroup, 0x01, kTypeUint8 }, -}; -const static std::vector fit_msg_fields_course_point = { - { kFieldCPTimeStamp, 0x04, kTypeUint32 }, - { kFieldCPPositionLat, 0x04, kTypeSint32 }, - { kFieldCPPositionLong, 0x04, kTypeSint32 }, - { kFieldCPDistance, 0x04, kTypeUint32 }, - { kFieldCPName, 0x10, kTypeString }, - { kFieldCPType, 0x01, kTypeEnum }, -}; -const static std::vector fit_msg_fields_record = { - { kFieldTimestamp, 0x04, kTypeUint32 }, - { kFieldLatitude, 0x04, kTypeSint32 }, - { kFieldLongitude, 0x04, kTypeSint32 }, - { kFieldDistance, 0x04, kTypeUint32 }, - { kFieldAltitude, 0x02, kTypeUint16 }, - { kFieldSpeed, 0x02, kTypeUint16 }, -}; - - -static void -fit_write_message_def(uint8_t local_id, uint16_t global_id, const std::vector &fields) { +void +GarminFitFormat::fit_write_message_def(uint8_t local_id, uint16_t global_id, const std::vector& fields) const +{ gbfputc(0x40 | local_id, fout); // Local ID gbfputc(0, fout); // Reserved gbfputc(0, fout); // Little endian gbfputuint16(global_id, fout); // Global ID gbfputc(fields.size(), fout); // Number of fields - for (auto &&field : fields) { + for (auto&& field : fields) { gbfputc(field.id, fout); // Field definition number gbfputc(field.size, fout); // Field size in bytes gbfputc(field.type, fout); // Field type } } - -static uint16_t -fit_crc16(uint8_t data, uint16_t crc) { +uint16_t +GarminFitFormat::fit_crc16(uint8_t data, uint16_t crc) +{ static const uint16_t crc_table[] = { 0x0000, 0xcc01, 0xd801, 0x1400, 0xf001, 0x3c00, 0x2800, 0xe401, 0xa001, 0x6c00, 0x7800, 0xb401, 0x5000, 0x9c01, 0x8801, 0x4400 @@ -964,9 +745,9 @@ fit_crc16(uint8_t data, uint16_t crc) { return crc; } - -static void -fit_write_timestamp(const gpsbabel::DateTime &t) { +void +GarminFitFormat::fit_write_timestamp(const gpsbabel::DateTime& t) const +{ uint32_t t_fit; if (t.isValid() && t.toTime_t() >= (unsigned int)GPS_Math_Gtime_To_Utime(0)) { t_fit = GPS_Math_Utime_To_Gtime(t.toTime_t()); @@ -976,9 +757,9 @@ fit_write_timestamp(const gpsbabel::DateTime &t) { gbfputuint32(t_fit, fout); } - -static void -fit_write_fixed_string(const QString &s, unsigned int len) { +void +GarminFitFormat::fit_write_fixed_string(const QString& s, unsigned int len) const +{ QString trimmed(s); QByteArray u8buf; @@ -999,9 +780,9 @@ fit_write_fixed_string(const QString &s, unsigned int len) { gbfwrite(u8buf.data(), len, 1, fout); } - -static void -fit_write_position(double pos) { +void +GarminFitFormat::fit_write_position(double pos) const +{ if (pos >= -180 && pos < 180) { gbfputint32(GPS_Math_Deg_To_Semi(pos), fout); } else { @@ -1009,12 +790,12 @@ fit_write_position(double pos) { } } - // Note: The data fields written using fit_write_msg_*() below need to match // the message field definitions in fit_msg_fields_* above! -static void -fit_write_msg_file_id(uint8_t type, uint16_t manufacturer, uint16_t product, - const gpsbabel::DateTime &time_created) { +void +GarminFitFormat::fit_write_msg_file_id(uint8_t type, uint16_t manufacturer, uint16_t product, + const gpsbabel::DateTime& time_created) const +{ gbfputc(kWriteLocalIdFileId, fout); gbfputc(type, fout); gbfputuint16(manufacturer, fout); @@ -1022,19 +803,21 @@ fit_write_msg_file_id(uint8_t type, uint16_t manufacturer, uint16_t product, fit_write_timestamp(time_created); } -static void -fit_write_msg_course(const QString &name, uint8_t sport) { +void +GarminFitFormat::fit_write_msg_course(const QString& name, uint8_t sport) const +{ gbfputc(kWriteLocalIdCourse, fout); fit_write_fixed_string(name, 0x10); gbfputc(sport, fout); } -static void -fit_write_msg_lap(const gpsbabel::DateTime ×tamp, const gpsbabel::DateTime &start_time, - double start_position_lat, double start_position_long, - double end_position_lat, double end_position_long, - uint32_t total_elapsed_time_s, double total_distance_m, - double avg_speed_ms, double max_speed_ms) { +void +GarminFitFormat::fit_write_msg_lap(const gpsbabel::DateTime& timestamp, const gpsbabel::DateTime& start_time, + double start_position_lat, double start_position_long, + double end_position_lat, double end_position_long, + uint32_t total_elapsed_time_s, double total_distance_m, + double avg_speed_ms, double max_speed_ms) const +{ gbfputc(kWriteLocalIdLap, fout); fit_write_timestamp(timestamp); fit_write_timestamp(start_time); @@ -1066,10 +849,10 @@ fit_write_msg_lap(const gpsbabel::DateTime ×tamp, const gpsbabel::DateTime } } - -static void -fit_write_msg_event(const gpsbabel::DateTime ×tamp, - uint8_t event, uint8_t event_type, uint8_t event_group) { +void +GarminFitFormat::fit_write_msg_event(const gpsbabel::DateTime& timestamp, + uint8_t event, uint8_t event_type, uint8_t event_group) const +{ gbfputc(kWriteLocalIdEvent, fout); fit_write_timestamp(timestamp); gbfputc(event, fout); @@ -1077,12 +860,12 @@ fit_write_msg_event(const gpsbabel::DateTime ×tamp, gbfputc(event_group, fout); } - -static void -fit_write_msg_course_point(const gpsbabel::DateTime ×tamp, - double position_lat, double position_long, - double distance_m, const QString &name, - uint8_t type) { +void +GarminFitFormat::fit_write_msg_course_point(const gpsbabel::DateTime& timestamp, + double position_lat, double position_long, + double distance_m, const QString& name, + uint8_t type) const +{ gbfputc(kWriteLocalIdCoursePoint, fout); fit_write_timestamp(timestamp); fit_write_position(position_lat); @@ -1096,12 +879,12 @@ fit_write_msg_course_point(const gpsbabel::DateTime ×tamp, gbfputc(type, fout); } - -static void -fit_write_msg_record(const gpsbabel::DateTime ×tamp, - double position_lat, double position_long, - double distance_m, double altitude, - double speed_ms) { +void +GarminFitFormat::fit_write_msg_record(const gpsbabel::DateTime& timestamp, + double position_lat, double position_long, + double distance_m, double altitude, + double speed_ms) const +{ gbfputc(kWriteLocalIdRecord, fout); fit_write_timestamp(timestamp); fit_write_position(position_lat); @@ -1123,9 +906,8 @@ fit_write_msg_record(const gpsbabel::DateTime ×tamp, } } - -static void -fit_write_file_header(uint32_t file_size, uint16_t crc) +void +GarminFitFormat::fit_write_file_header(uint32_t file_size, uint16_t crc) const { gbfputc(kWriteHeaderCrcLen, fout); // Header+CRC length gbfputc(0x10, fout); // Protocol version @@ -1135,9 +917,8 @@ fit_write_file_header(uint32_t file_size, uint16_t crc) gbfputuint16(crc, fout); // CRC } - -static void -fit_write_header_msgs(const gpsbabel::DateTime& ctime, const QString& name) +void +GarminFitFormat::fit_write_header_msgs(const gpsbabel::DateTime& ctime, const QString& name) const { fit_write_message_def(kWriteLocalIdFileId, kIdFileId, fit_msg_fields_file_id); fit_write_message_def(kWriteLocalIdCourse, kIdCourse, fit_msg_fields_course); @@ -1150,9 +931,8 @@ fit_write_header_msgs(const gpsbabel::DateTime& ctime, const QString& name) fit_write_msg_course(name, 0); } - -static void -fit_write_file_finish() +void +GarminFitFormat::fit_write_file_finish() const { // Update data records size in file header gbsize_t file_size = gbftell(fout); @@ -1165,7 +945,7 @@ fit_write_file_finish() // Update file header CRC uint16_t crc = 0; gbfseek(fout, 0, SEEK_SET); - for (unsigned int i = 0; i < kWriteHeaderLen; i++) { + for (unsigned int i = 0; i < kWriteHeaderLen; ++i) { int data = gbfgetc(fout); if (data == EOF) { fatal(MYNAME ": File %s truncated\n", fout->name); @@ -1188,21 +968,21 @@ fit_write_file_finish() gbfputuint16(crc, fout); } -static void -fit_collect_track_hdr(const route_head *rte) +void +GarminFitFormat::fit_collect_track_hdr(const route_head* rte) { (void)rte; course.clear(); } -static void -fit_collect_trackpt(const Waypoint* waypointp) +void +GarminFitFormat::fit_collect_trackpt(const Waypoint* waypointp) { course.push_back(FitCourseRecordPoint(*waypointp, false)); } -static void -fit_collect_track_tlr(const route_head *rte) +void +GarminFitFormat::fit_collect_track_tlr(const route_head* rte) { // Prepare for writing a course corresponding to a track. // For this, we need to check for/synthesize missing information @@ -1215,7 +995,7 @@ fit_collect_track_tlr(const route_head *rte) double prev_lat = 999, prev_lon = 999; double max_speed = 0; gpsbabel::DateTime prev_time; - for (auto &crpt: course) { + for (auto& crpt: course) { // Distance to prev. point double dist; if (crpt.odometer_distance && crpt.odometer_distance >= dist_sum) { @@ -1257,11 +1037,11 @@ fit_collect_track_tlr(const route_head *rte) // Insert course points at the right place between track points (with // minimum distance to next track point) while (!waypoints.empty()) { - auto &wpt = waypoints.front(); + auto& wpt = waypoints.front(); double best_distance = -1; auto best_distance_it = course.begin(); double best_odometer_distance = 0; - for (auto cit = course.begin(); cit != course.end(); cit++) { + for (auto cit = course.begin(); cit != course.end(); ++cit) { if (!cit->is_course_point) { double distance = gcgeodist(cit->lat, cit->lon, wpt.lat, wpt.lon); if (best_distance < 0 || distance < best_distance) { @@ -1309,7 +1089,7 @@ fit_collect_track_tlr(const route_head *rte) fit_write_msg_event(track_date_time, kEventTimer, kEventTypeStart, 0); // Write track/course points for the whole track - for (auto &crpt: course) { + for (auto& crpt: course) { if (crpt.is_course_point) { fit_write_msg_course_point(crpt.creation_time, crpt.lat, @@ -1330,14 +1110,14 @@ fit_collect_track_tlr(const route_head *rte) fit_write_msg_event(track_end_date_time, kEventTimer, kEventTypeStopDisableAll, 0); } -static void -fit_collect_waypt(const Waypoint* waypointp) +void +GarminFitFormat::fit_collect_waypt(const Waypoint* waypointp) { FitCourseRecordPoint crpt(*waypointp, true); // Try to find a better course point type than "generic", based on the // course point name - for (auto &cptm: kCoursePointTypeMapping) { + for (auto& cptm: kCoursePointTypeMapping) { if (crpt.shortname.contains(cptm.first, Qt::CaseInsensitive)) { crpt.course_point_type = cptm.second; break; @@ -1347,44 +1127,29 @@ fit_collect_waypt(const Waypoint* waypointp) waypoints.push_back(crpt); } - - /******************************************************************************* * fit_write- global entry point *******************************************************************************/ -static void -fit_write() +void +GarminFitFormat::write() { fit_write_file_header(0, 0); write_header_msgs = true; - waypt_disp_all(fit_collect_waypt); - track_disp_all(fit_collect_track_hdr, fit_collect_track_tlr, fit_collect_trackpt); + + auto fit_collect_waypt_lambda = [this](const Waypoint* waypointp)->void { + fit_collect_waypt(waypointp); + }; + waypt_disp_all(fit_collect_waypt_lambda); + + auto fit_collect_track_hdr_lambda = [this](const route_head* rte)->void { + fit_collect_track_hdr(rte); + }; + auto fit_collect_track_tlr_lambda = [this](const route_head* rte)->void { + fit_collect_track_tlr(rte); + }; + auto fit_collect_trackpt_lambda = [this](const Waypoint* waypointp)->void { + fit_collect_trackpt(waypointp); + }; + track_disp_all(fit_collect_track_hdr_lambda, fit_collect_track_tlr_lambda, fit_collect_trackpt_lambda); fit_write_file_finish(); } - -/**************************************************************************/ - -// capabilities below means: we can only read and write waypoints -// please change this depending on your new module - -ff_vecs_t format_fit_vecs = { - ff_type_file, - { - ff_cap_write /* waypoints */, - (ff_cap)(ff_cap_read | ff_cap_write) /* tracks */, - ff_cap_none /* routes */ - }, - fit_rd_init, - fit_wr_init, - fit_rd_deinit, - fit_wr_deinit, - fit_read, - fit_write, - nullptr, - &fit_args, - CET_CHARSET_ASCII, 0 /* ascii is the expected character set */ - /* not fixed, can be changed through command line parameter */ - , NULL_POS_OPS, - nullptr -}; -/**************************************************************************/ diff --git a/garmin_fit.h b/garmin_fit.h new file mode 100644 index 000000000..e5f697c42 --- /dev/null +++ b/garmin_fit.h @@ -0,0 +1,342 @@ +/* + + Support for FIT track files. + + Copyright (C) 2011 Paul Brook, paul@nowt.org + Copyright (C) 2003-2011 Robert Lipe, robertlipe+source@gpsbabel.org + Copyright (C) 2019 Martin Buck, mb-tmp-tvguho.pbz@gromit.dyndns.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + */ +#ifndef GARMIN_FIT_H_INCLUDED_ +#define GARMIN_FIT_H_INCLUDED_ + +#include // for uint8_t, uint16_t, uint32_t +#include // for deque +#include // for pair +#include // for vector + +#include // for QList +#include // for QString +#include // for QVector + +#include "defs.h" +#include "format.h" // for Format +#include "gbfile.h" // for gbfile +#include "src/core/datetime.h" // for DateTime + + +class GarminFitFormat : public Format +{ +public: + QVector* get_args() override + { + return &fit_args; + } + + ff_type get_type() const override + { + return ff_type_file; + } + + QVector get_cap() const override + { + return { + ff_cap_write, /* waypoints */ + (ff_cap)(ff_cap_read | ff_cap_write), /* tracks */ + ff_cap_none /* routes */ + }; + } + + QString get_encode() const override + { + return CET_CHARSET_ASCII; + } + + int get_fixed_encode() const override + { + return 0; + } + + void rd_init(const QString& fname) override; + void read() override; + void rd_deinit() override; + void wr_init(const QString& fname) override; + void write() override; + void wr_deinit() override; + +private: + /* Types */ + + struct fit_field_t { + /* MSVC 2015 generates C2664 errors without some help. */ +#if defined(_MSC_VER) && (_MSC_VER < 1910) /* MSVC 2015 or earlier */ + fit_field_t() = default; + fit_field_t(int i, int s, int t) : id(i), size(s), type(t) {} +#endif + int id{}; + int size{}; + int type{}; + }; + + struct fit_message_def { + int endian{}; + int global_id{}; + QList fields; + }; + + struct fit_data_t { + int len{}; + int endian{}; + route_head* track{nullptr}; + uint32_t last_timestamp{}; + uint32_t global_utc_offset{}; + fit_message_def message_def[16]; + }; + + struct FitCourseRecordPoint { + FitCourseRecordPoint(const Waypoint& wpt, bool is_course_point, unsigned int course_point_type = kCoursePointTypeGeneric) + : lat(wpt.latitude), + lon(wpt.longitude), + altitude(wpt.altitude), + speed(WAYPT_HAS((&wpt), speed) ? wpt.speed : -1), + odometer_distance(wpt.odometer_distance), + creation_time(wpt.creation_time), + shortname(wpt.shortname), + is_course_point(is_course_point), + course_point_type(course_point_type) { } + double lat, lon, altitude; + double speed, odometer_distance; + gpsbabel::DateTime creation_time; + QString shortname; + bool is_course_point; + unsigned int course_point_type; + }; + + /* Constants */ + +// constants for global IDs + static constexpr int kIdFileId = 0; + static constexpr int kIdDeviceSettings = 0; + static constexpr int kIdLap = 19; + static constexpr int kIdRecord = 20; + static constexpr int kIdEvent = 21; + static constexpr int kIdCourse = 31; + static constexpr int kIdCoursePoint = 32; + +// constants for local IDs (for writing) + static constexpr int kWriteLocalIdFileId = 0; + static constexpr int kWriteLocalIdCourse = 1; + static constexpr int kWriteLocalIdLap = 2; + static constexpr int kWriteLocalIdEvent = 3; + static constexpr int kWriteLocalIdCoursePoint = 4; + static constexpr int kWriteLocalIdRecord = 5; + +// constants for message fields +// for all global IDs + static constexpr int kFieldTimestamp = 253; + static constexpr int kFieldMessageIndex = 254; +// for global ID: file id + static constexpr int kFieldType = 0; + static constexpr int kFieldManufacturer = 1; + static constexpr int kFieldProduct = 2; + static constexpr int kFieldTimeCreated = 4; +// for global ID: device settings + static constexpr int kFieldGlobalUtcOffset = 4; +// for global ID: lap + static constexpr int kFieldStartTime = 2; + static constexpr int kFieldStartLatitude = 3; + static constexpr int kFieldStartLongitude = 4; + static constexpr int kFieldEndLatitude = 5; + static constexpr int kFieldEndLongitude = 6; + static constexpr int kFieldElapsedTime = 7; + static constexpr int kFieldTotalTimerTime = 8; + static constexpr int kFieldTotalDistance = 9; + static constexpr int kFieldAvgSpeed = 13; + static constexpr int kFieldMaxSpeed = 14; +// for global ID: record + static constexpr int kFieldLatitude = 0; + static constexpr int kFieldLongitude = 1; + static constexpr int kFieldAltitude = 2; + static constexpr int kFieldHeartRate = 3; + static constexpr int kFieldCadence = 4; + static constexpr int kFieldDistance = 5; + static constexpr int kFieldSpeed = 6; + static constexpr int kFieldPower = 7; + static constexpr int kFieldTemperature = 13; + static constexpr int kFieldEnhancedSpeed = 73; + static constexpr int kFieldEnhancedAltitude = 78; +// for global ID: event + static constexpr int kFieldEvent = 0; + static constexpr int kEnumEventTimer = 0; + static constexpr int kFieldEventType = 1; + static constexpr int kEnumEventTypeStart = 0; + static constexpr int kFieldEventGroup = 4; +// for global ID: course + static constexpr int kFieldSport = 4; + static constexpr int kFieldName = 5; +// for global ID: course point + static constexpr int kFieldCPTimeStamp = 1; + static constexpr int kFieldCPPositionLat = 2; + static constexpr int kFieldCPPositionLong = 3; + static constexpr int kFieldCPDistance = 4; + static constexpr int kFieldCPName = 6; + static constexpr int kFieldCPType = 5; + +// For developer fields as a non conflicting id + static constexpr int kFieldInvalid = 255; + +// types for message definitions + static constexpr int kTypeEnum = 0x00; + static constexpr int kTypeUint8 = 0x02; + static constexpr int kTypeString = 0x07; + static constexpr int kTypeUint16 = 0x84; + static constexpr int kTypeSint32 = 0x85; + static constexpr int kTypeUint32 = 0x86; + +// misc. constants for message fields + static constexpr int kFileCourse = 0x06; + static constexpr int kEventTimer = 0x00; + static constexpr int kEventTypeStart = 0x00; + static constexpr int kEventTypeStopDisableAll = 0x09; + static constexpr int kCoursePointTypeGeneric = 0x00; + static constexpr int kCoursePointTypeLeft = 0x06; + static constexpr int kCoursePointTypeRight = 0x07; + + static constexpr int kWriteHeaderLen = 12; + static constexpr int kWriteHeaderCrcLen = 14; + + static constexpr double kSynthSpeed = 10.0 * 1000 / 3600; /* speed in m/s */ + + /* Member Functions */ + + void fit_parse_header(); + uint8_t fit_getuint8(); + uint16_t fit_getuint16(); + uint32_t fit_getuint32(); + void fit_parse_definition_message(uint8_t header); + uint32_t fit_read_field(const fit_field_t& f); + void fit_parse_data(const fit_message_def& def, int time_offset); + void fit_parse_data_message(uint8_t header); + void fit_parse_compressed_message(uint8_t header); + void fit_parse_record(); + void fit_write_message_def(uint8_t local_id, uint16_t global_id, const std::vector& fields) const; + static uint16_t fit_crc16(uint8_t data, uint16_t crc); + void fit_write_timestamp(const gpsbabel::DateTime& t) const; + void fit_write_fixed_string(const QString& s, unsigned int len) const; + void fit_write_position(double pos) const; + void fit_write_msg_file_id(uint8_t type, uint16_t manufacturer, uint16_t product, const gpsbabel::DateTime& time_created) const; + void fit_write_msg_course(const QString& name, uint8_t sport) const; + void fit_write_msg_lap(const gpsbabel::DateTime& timestamp, const gpsbabel::DateTime& start_time, double start_position_lat, double start_position_long, double end_position_lat, double end_position_long, uint32_t total_elapsed_time_s, double total_distance_m, double avg_speed_ms, double max_speed_ms) const; + void fit_write_msg_event(const gpsbabel::DateTime& timestamp, uint8_t event, uint8_t event_type, uint8_t event_group) const; + void fit_write_msg_course_point(const gpsbabel::DateTime& timestamp, double position_lat, double position_long, double distance_m, const QString& name, uint8_t type) const; + void fit_write_msg_record(const gpsbabel::DateTime& timestamp, double position_lat, double position_long, double distance_m, double altitude, double speed_ms) const; + void fit_write_file_header(uint32_t file_size, uint16_t crc) const; + void fit_write_header_msgs(const gpsbabel::DateTime& ctime, const QString& name) const; + void fit_write_file_finish() const; + void fit_collect_track_hdr(const route_head* rte); + void fit_collect_trackpt(const Waypoint* waypointp); + void fit_collect_track_tlr(const route_head* rte); + void fit_collect_waypt(const Waypoint* waypointp); + + /* Data Members */ + + char* opt_allpoints = nullptr; + int lap_ct = 0; + bool new_trkseg = false; + bool write_header_msgs = false; + + QVector fit_args = { + { + "allpoints", &opt_allpoints, + "Read all points even if latitude or longitude is missing", + nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr + }, + }; + + const std::vector > kCoursePointTypeMapping = { + {"left", kCoursePointTypeLeft}, + {"links", kCoursePointTypeLeft}, + {"gauche", kCoursePointTypeLeft}, + {"izquierda", kCoursePointTypeLeft}, + {"sinistra", kCoursePointTypeLeft}, + + {"right", kCoursePointTypeRight}, + {"rechts", kCoursePointTypeRight}, + {"droit", kCoursePointTypeRight}, + {"derecha", kCoursePointTypeRight}, + {"destro", kCoursePointTypeRight}, + }; + + fit_data_t fit_data; + + std::deque course, waypoints; + + gbfile* fin{nullptr}; + gbfile* fout{nullptr}; + + /******************************************************************************* + * FIT writing + *******************************************************************************/ + + const std::vector fit_msg_fields_file_id = { + // field id, size, type + { kFieldType, 0x01, kTypeEnum }, + { kFieldManufacturer, 0x02, kTypeUint16 }, + { kFieldProduct, 0x02, kTypeUint16 }, + { kFieldTimeCreated, 0x04, kTypeUint32 }, + }; + const std::vector fit_msg_fields_course = { + { kFieldName, 0x10, kTypeString }, + { kFieldSport, 0x01, kTypeEnum }, + }; + const std::vector fit_msg_fields_lap = { + { kFieldTimestamp, 0x04, kTypeUint32 }, + { kFieldStartTime, 0x04, kTypeUint32 }, + { kFieldStartLatitude, 0x04, kTypeSint32 }, + { kFieldStartLongitude, 0x04, kTypeSint32 }, + { kFieldEndLatitude, 0x04, kTypeSint32 }, + { kFieldEndLongitude, 0x04, kTypeSint32 }, + { kFieldElapsedTime, 0x04, kTypeUint32 }, + { kFieldTotalTimerTime, 0x04, kTypeUint32 }, + { kFieldTotalDistance, 0x04, kTypeUint32 }, + { kFieldAvgSpeed, 0x02, kTypeUint16 }, + { kFieldMaxSpeed, 0x02, kTypeUint16 }, + }; + const std::vector fit_msg_fields_event = { + { kFieldTimestamp, 0x04, kTypeUint32 }, + { kFieldEvent, 0x01, kTypeEnum }, + { kFieldEventType, 0x01, kTypeEnum }, + { kFieldEventGroup, 0x01, kTypeUint8 }, + }; + const std::vector fit_msg_fields_course_point = { + { kFieldCPTimeStamp, 0x04, kTypeUint32 }, + { kFieldCPPositionLat, 0x04, kTypeSint32 }, + { kFieldCPPositionLong, 0x04, kTypeSint32 }, + { kFieldCPDistance, 0x04, kTypeUint32 }, + { kFieldCPName, 0x10, kTypeString }, + { kFieldCPType, 0x01, kTypeEnum }, + }; + const std::vector fit_msg_fields_record = { + { kFieldTimestamp, 0x04, kTypeUint32 }, + { kFieldLatitude, 0x04, kTypeSint32 }, + { kFieldLongitude, 0x04, kTypeSint32 }, + { kFieldDistance, 0x04, kTypeUint32 }, + { kFieldAltitude, 0x02, kTypeUint16 }, + { kFieldSpeed, 0x02, kTypeUint16 }, + }; +}; +#endif // GARMIN_FIT_H_INCLUDED_ diff --git a/vecs.h b/vecs.h index 7a0d57fa3..e482c4c2f 100644 --- a/vecs.h +++ b/vecs.h @@ -30,6 +30,7 @@ #include "defs.h" #include "format.h" #include "energympro.h" +#include "garmin_fit.h" #include "geojson.h" #include "ggv_bin.h" #include "globalsat_sport.h" @@ -172,7 +173,6 @@ extern ff_vecs_t miniHomer_vecs; extern ff_vecs_t jogmap_vecs; extern ff_vecs_t wintec_tes_vecs; extern ff_vecs_t format_garmin_xt_vecs; -extern ff_vecs_t format_fit_vecs; extern ff_vecs_t mapbar_track_vecs; extern ff_vecs_t f90g_track_vecs; extern ff_vecs_t mapfactor_vecs; @@ -402,7 +402,7 @@ private: LegacyFormat wintec_tes_fmt {wintec_tes_vecs}; SubripFormat subrip_fmt; LegacyFormat format_garmin_xt_fmt {format_garmin_xt_vecs}; - LegacyFormat format_fit_fmt {format_fit_vecs}; + GarminFitFormat format_fit_fmt; LegacyFormat mapbar_track_fmt {mapbar_track_vecs}; LegacyFormat f90g_track_fmt {f90g_track_vecs}; LegacyFormat mapfactor_fmt {mapfactor_vecs}; -- 2.30.2